/**
 * @license
 * Copyright 2021 Du Tian Wei
 * SPDX-License-Identifier: Apache-2.0
 */
"use strict"
import * as util from './util.mjs'
import * as obvm from './vm.mjs'

/**
 * debugger连接器，实际应用场景应该实现子类
 */
class DebuggerBroker {
    debugger_;
    vm;
    onEvent(eventName, args, level, block, stack) { }
    onMessage(msg) {
        let method = 'cmd_' + msg.method;
        if (this[method]) this[method].apply(this, msg.args);
    }
    setDebugger(debugger_) {
        this.debugger_ = debugger_;
    }
    setVM(vm) {
        this.vm = vm;
    }
    getDebugger() {
        return this.debugger_;
    }
    cmd_addBreakpoint(breakpoint) {
        this.getDebugger().addBreakpoint(breakpoint);
    }
    cmd_removeBreakpoint(breakpoint) {
        this.getDebugger().removeBreakpoint(breakpoint);
    }
    cmd_listFSM() {
        let brokenFSMID = this.getDebugger().BEMapStack[0]?.state.fsm.id;
        let fsmlist = Object.values(this.getDebugger().vm.fsmManager.RunningByID);
        let info = fsmlist.map(fsm => {
            return {
                id: fsm.id,
                name: fsm.data.FullName,
                state: fsm.CurrentState.data.Name,
                pendingMessageCount: fsm.Inbox.length,
                broken: fsm.id == brokenFSMID,
            };
        });
        this.onEvent('listFSM_reply', [info], null, null, null);
    }
    cmd_pause() {
        this.vm.paused = new obvm.VMPausedException('paused by debugger');
        this.onEvent('VMPaused', ['paused by debugger']);
    }
    cmd_resume() {
        let pausedException = this.vm.paused;
        if (pausedException instanceof obvm.BreakpointException) {
            this.debugger_.resume();
            this.onEvent('VMResumed');
        } else if (pausedException instanceof obvm.VMPausedException) {
            this.vm.paused = null;
            this.onEvent('VMResumed');
        } else {
            console.log('unknown pause exception: ', pausedException);
            throw new Error('unknown pause exception: ', pausedException);
        }
    }
    cmd_getStack(id) {
        let stack = this.getDebugger().BEMapStack;
        let brokenFSMID = stack[0]?.state.fsm.id;
        if (id == brokenFSMID) {
            let info = [];
            for (let i = 0; i < stack.length; ++i) {
                let frame = stack[i];
                info.push({ inst: frame.inst });
            }
            this.onEvent('getStack_reply', [id, info]);
        }
    }
    cmd_getStackDetial(frameIdx) {
        let frame = this.getDebugger().BEMapStack[frameIdx];
        let map = frame.map;
        let info = [];
        for (const [key, v] of map.entries()) {
            let value = v.value;
            if (value instanceof obvm.OBVMFSM) {
                value = value.data.FullName + "#" + value.id;
            } else if (typeof (value) === 'object') {
                value = value.toString ? value.toString() : value.constructor.name;
            }
            info.push([key, v.valueRegisterType, value]);
        }
        this.onEvent('getStackDetial_reply', [frameIdx, info]);
    }
}
class LogViewBroker extends DebuggerBroker {
    constructor(logView, opt) {
        super(opt);
        this.listener = logView
    }

    onEvent(name, args, level, block, stack) {
        super.onEvent(name, block, args, level, stack);
        let view = this.listener.$refs.debugview;;
        let callback = view['on' + name];
        if (callback) {
            callback.apply(view, args);
        } else {
            console.warn('no listener for event: ' + name);
        }
    }
}
class Debugger {
    /**
     * 
     * @param {connector:DebuggerConnector} options 
     */
    breakpoints = new Set();
    BEMapStack = [];
    DBEMap = new Map();
    DBEMapLoopStack = [];
    LoopRecord = new Set();
    // invokeStack = [];
    vm;
    constructor(options) {
        this.options = options || {};
        if (this.options.breakpoints) {
            for (let breakpoint of this.options.breakpoints) {
                this.addBreakpoint(breakpoint);
            }
        }
        this.instructionCount = 0;

        if (this.options.broker) {
            this.broker = this.options.broker;
            this.broker.setDebugger(this);
        }
    }
    setVM(vm) {
        this.reset();
        this.vm = vm;
        if (this.broker) {
            this.broker.setVM(vm)
        }
    }
    reset() {
        this.instructionCount = 0;
        this.BEMapStack = [];
        this.DBEMap = new Map();
        this.invokeStack = [];
        this.LoopRecord = new Set();
        this.DBEMapLoopStack = [];
    }
    addBreakpoint(breakpoint) {
        this.breakpoints.add(breakpoint);
    }
    removeBreakpoint(breakpoint) {
        this.breakpoints.delete(breakpoint);
    }
    hasBreakpointByKey(key) {
        return this.breakpoints.has(key);
    }
    static pausableInstructionWrap(instruction) {
        return function (st) {
            if (st.fsm.VM.pausing) {
                st.fsm.VM.pausing = false;
                throw new obvm.VMPausedException();
            }
            return instruction.apply(null, arguments)
        };
    }
    instructionWrap(instruction) {
        if (this.options.instructionWrap) {
            instruction = this.options.instructionWrap(instruction);
        }
        let that = this;
        return function () {
            that.instructionCount++;
            // console.log(that.instructionCount);
            return instruction.apply(null, arguments);
        };
    }
    registerWrap(register) {
        if (this.options.registerWrap) {
            register = this.options.registerWrap(register);
        }
        let that = this;
        return function () {
            that.instructionCount++;
            let ret = register.apply(null, arguments);
            return ret;
        };
    }
    beforeInstruction(dbi, st, uf, locals, pos) {
        let block = dbi.BlockId;
        this.BEMapStack[this.BEMapStack.length - 1].inst = block;
        if (this.hasBreakpointByKey(block)) {
            throw new obvm.BreakpointException(dbi, st, uf, locals, pos, this.BEMapStack);
        }
    }
    interExpr(dbe, st, uf, locals, pos) {
        let block = dbe.BlockId;
        if (this.hasBreakpointByKey(block)) {
            throw new obvm.BreakpointException(dbe, st, uf, locals, pos, this.BEMapStack);
        }
    }
    exitExpr(value, valueRegisterType, dbe, st, uf, locals, pos) {
        this.DBEMap.set(dbe.BlockId, { value, valueRegisterType });
        this.LoopRecord.add(dbe.BlockId);
    }
    printStack() {
    }
    getResult(block) {
        let v = this.DBEMap.get(block.BlockId);
        return v.value;
    }
    hasResult(block) {
        return this.DBEMap.has(block.BlockId);
    }
    debugEvent(eventName, block, args, level, stack) {
        if (this.broker) {
            if (typeof (level) != 'number') {
                console.log('level is not a number ' + level);
                level = 3
            }
            this.broker.onEvent(eventName, args, level, block, this.stackMsg(stack));
        }
    }
    stackMsg(stack) {
        // if (stack) {
        //     return stack.map(stackItem => {

        //     });
        // }
    }
    isInStack(rrfunc) {
        return this.BEMapStack.some(item => item.rrfunc == rrfunc);
    }
    setVMState(breakpointException) {
        this.VMState = breakpointException;
    }
    VMPaused(reason) {
        this.broker.onEvent('VMPaused', [reason]);
    }
    resume() {
        this.vm.paused = null;
        this.resumed = true;
    }
    onVMUpdate() {
        if (this.resumed) {
            this.resumed = false;
            this.vm.fsmManager.Pending.unshift(this.VMState.st.fsm);
            if (this.VMState.dbb.constructor.name == 'DBE') {
                // TODO
            } else if (this.VMState.dbb.constructor.name == 'DBI') {
                this.BEMapStack[this.BEMapStack.length - 1].rrfunc.pos++;
            } else if (this.VMState.dbb.constructor.name == 'B_DBSS') {
                this.BEMapStack[this.BEMapStack.length - 1].rrfunc.pos++;
            }
            this.BEMapStack[0].rrfunc.Call();
        }
    }
    pushStack(rrfunc, localVars, state) {
        this.DBEMap = new Map();
        this.LoopRecord = new Set();
        this.DBEMapLoopStack = [];
        this.BEMapStack.push({ rrfunc, localVars, state, map: this.DBEMap, loopRecord: this.LoopRecord, DBEMapLoopStack: this.DBEMapLoopStack, inst: null });
    }
    popStack() {
        // let item = 
        this.BEMapStack.pop();
        let item = this.BEMapStack[this.BEMapStack.length - 1];
        if (item) {
            this.DBEMap = item.map;
            this.LoopRecord = item.loopRecord;
            this.DBEMapLoopStack = item.DBEMapLoopStack;
        } else {
            this.reset();
        }
    }
    statementStart(dbls, st, uf, locals, pos) {
        let block = dbls.stmtBlockID;
        this.DBEMapLoopStack.push(this.LoopRecord);
        this.LoopRecord = new Set();
        if (this.hasBreakpointByKey(block)) {
            throw new obvm.BreakpointException(dbls, st, uf, locals, pos, this.BEMapStack);
        }
    }
    statementEnd(dble, st, uf, locals, pos) {
        this.LoopRecord.forEach(blockId => {
            this.DBEMap.delete(blockId);
        });
        this.LoopRecord = this.DBEMapLoopStack.pop();
    }
    afterChangeState(fsm) {
        this.reset();
    }
}
export {
    // DebuggerableVM, 
    DebuggerBroker, Debugger, LogViewBroker
}
